#include <cstdlib>
#include <cstring>
#include <fstream>
#include <glog/logging.h>
#include <iostream>

#include "evdeploy/deploy.h"
#include "ji_utils.h"
#include "sample_algorithm.h"
#include <queue>

#define JSON_ALERT_FLAG_KEY ("is_alert")
#define JSON_ALERT_FLAG_TRUE true
#define JSON_ALERT_FLAG_FALSE false

using namespace ev;

SampleAlgorithm::SampleAlgorithm() {}

SampleAlgorithm::~SampleAlgorithm() { UnInit(); }

JiErrorCode SampleAlgorithm::Init()
{
    // 从默认的配置文件读取相关配置参数
    const char *config_file = "/usr/local/ev_sdk/config/algo_config.json";
    SDKLOG(INFO) << "Parsing configuration file: " << config_file;
    std::ifstream conf(config_file);
    if (conf.is_open())
    {
        size_t len = GetFileLen(conf);
        char *conf_str = new char[len + 1];
        conf.read(conf_str, len);
        conf_str[len] = '\0';
        SDKLOG(INFO) << "Configs:" << conf_str;
        m_config.ParseAndUpdateArgs(conf_str);
        delete[] conf_str;
        conf.close();
    }
    m_detector = std::make_shared<SampleDetector>();
    // 注意uuid,可以从algo_config.json中去配置获取,也可以直接给出
    auto ret = m_detector->Init(&m_config.algo_config);
    if (!ret)
    {
        SDKLOG(WARNING) << "detector init failed";
        return JiErrorCode::JISDK_RET_FAILED;
    }
    // 加载字库
    m_ft2 = cv::freetype::createFreeType2();
    m_ft2->loadFontData(DEFAULT_FONT_PATH, 0);
    return JISDK_RET_SUCCEED;
}

JiErrorCode SampleAlgorithm::UnInit()
{
    if (m_detector)
    {
        SDKLOG(INFO) << "uninit";
        m_detector->UnInit();
    }
    return JISDK_RET_SUCCEED;
}

JiErrorCode SampleAlgorithm::UpdateConfig(const char *args)
{
    if (!args)
    {
        SDKLOG(ERROR) << "m_config string is null ";
        return JISDK_RET_FAILED;
    }
    m_config.ParseAndUpdateArgs(args);
    return JISDK_RET_SUCCEED;
}

JiErrorCode SampleAlgorithm::GetOutFrame(JiImageInfo **out, unsigned int &out_count)
{
    out_count = m_out_count;
    m_out_image[0].nWidth = m_output_frame.cols;
    m_out_image[0].nHeight = m_output_frame.rows;
    m_out_image[0].nFormat = JI_IMAGE_TYPE_BGR;
    m_out_image[0].nDataType = JI_UNSIGNED_CHAR;
    m_out_image[0].nWidthStride = m_output_frame.step[0];
    m_out_image[0].pData = m_output_frame.data;
    *out = m_out_image;
    return JISDK_RET_SUCCEED;
}

JiErrorCode SampleAlgorithm::Process(const cv::Mat &in_frame, const char *args, JiEvent &event)
{
    // 输入图片为空的时候直接返回错误
    if (in_frame.empty())
    {
        SDKLOG(ERROR) << "Invalid input!";
        return JISDK_RET_FAILED;
    }

    // 由于roi配置是归一化的坐标,所以输出图片的大小改变时,需要更新ROI的配置
    if (in_frame.cols != m_config.current_in_frame_size.width || in_frame.rows != m_config.current_in_frame_size.height)
    {
        SDKLOG(INFO) << "Update ROI Info...";
        m_config.UpdateROIInfo(in_frame.cols, in_frame.rows);
    }

    // 如果输入的参数不为空且与上一次的参数不完全一致,需要调用更新配置的接口
    if (args != nullptr && m_str_last_arg != args)
    {
        m_str_last_arg = args;
        SDKLOG(INFO) << "Update args:" << args;
        m_config.ParseAndUpdateArgs(args);
    }

    // 针对整张图进行推理,获取所有的检测目标,并过滤出在ROI内的目标
    std::vector<ev::vision::BoxInfo> detected_objects; // 所有检测目标
    std::vector<ev::vision::BoxInfo> valid_targets;    // 输出筛选之后的有效目标
    std::vector<ev::vision::BoxInfo> in_valid_targets; // 用于渲染的无效目标
    std::vector<int> target_roi_indexs;                // 输出目标的ROI名称索引

    // 算法处理
    cv::Mat img = in_frame.clone();

    m_detector->ProcessImage(img, detected_objects);

    // 挑选出行人&过滤掉不在roi内的目标
    for (auto iter = detected_objects.begin(); iter != detected_objects.end();)
    {
        if (iter->label == 0) // 筛选符合条件的类别
        {
            int roi_index{-1};
            for (int i = 0; i < m_config.current_roi_orig_polygons.size(); i++)
            {
                int mid_x = (iter->x1 + iter->x2) / 2;
                int mid_y = (iter->y1 + iter->y2) / 2;
                // 当检测的目标的中心点在ROI内的话，就视为闯入ROI的有效目标
                if (WKTParser::InPolygon(m_config.current_roi_orig_polygons[i], cv::Point(mid_x, mid_y)))
                {
                    roi_index = i;
                    break;
                }
            }
            if (roi_index >= 0) // 筛选在roi内的目标
            {
                valid_targets.push_back(*iter);                
                target_roi_indexs.push_back(roi_index);
                iter++;
                continue;
            }
        }
        in_valid_targets.push_back(*iter);
        iter++;
    }

    SDKLOG_FIRST_N(INFO, 5) << "invalid targets : " << in_valid_targets.size()
                            << " valid targets :  " << valid_targets.size();
    REC_TIME(t0);

    // 此处示例业务逻辑：当算法检测到有行人闯入时，就报警
    bool is_need_alert = false;
    float time_elapsed{0.0f}; // 人员闯入的持续时间，m_config.recoder_config.enable=true时才计算

    if (m_config.recoder_config.enable) // 是否开启告警目标放漏检逻辑recorder
    {
        if (m_cid_recoders.find(m_config.cid) == m_cid_recoders.end()) // 不同cid使用不同的recorder
        {
            m_cid_recoders[m_config.cid] = std::make_shared<EVAlertRecorder>(m_config.recoder_config);
        }
        auto frame_result = m_cid_recoders[m_config.cid]->push(
            valid_targets.size() > 0, std::chrono::steady_clock::now()); // 计算是否需要抛出告警
        is_need_alert = frame_result.first;
        time_elapsed = frame_result.second;
    }
    else
    {
        is_need_alert = valid_targets.size() > 0; // 在不开启recorder的情况下，检测到有效目标就抛出告警
    }

    // 创建输出图
    in_frame.copyTo(m_output_frame);
    // 画ROI区域
    if (m_config.draw_roi_area && !m_config.current_roi_orig_polygons.empty())
    {
        DrawPolygon(m_output_frame, m_config.current_roi_orig_polygons,
                    cv::Scalar(m_config.roi_color[0], m_config.roi_color[1], m_config.roi_color[2]),
                    m_config.roi_color[3], cv::LINE_AA, m_config.roi_line_thickness, m_config.roi_fill);
    }

    // 并将检测到的在ROI内部的目标画到图上
    auto &label_map = m_config.target_rect_text_map[m_config.language]; // 对应语言的标签映射
    if (m_config.draw_result)
    {
        for (auto &object : valid_targets)
        {
            std::stringstream ss;
            ss << (label_map.find(object.label) == label_map.end() ? "" : label_map[object.label]);
            if (m_config.draw_confidence)
            {
                ss.precision(0);
                ss << std::fixed << (label_map.find(object.label) == label_map.end() ? "" : ":") << object.score * 100
                   << "%";
            }
            cv::Rect rect = cv::Rect{static_cast<int>(object.x1), static_cast<int>(object.y1),
                                     static_cast<int>(object.x2 - object.x1), static_cast<int>(object.y2 - object.y1)};
            DrawRectText(
                m_output_frame, rect, ss.str(), m_config.target_rect_line_thickness, cv::LINE_AA,
                cv::Scalar(m_config.target_rect_color[0], m_config.target_rect_color[1], m_config.target_rect_color[2]),
                m_config.target_rect_color[3], m_config.target_text_height,
                cv::Scalar(m_config.text_foreground_color[0], m_config.text_foreground_color[1],
                           m_config.text_foreground_color[2]),
                cv::Scalar(m_config.text_background_color[0], m_config.text_background_color[1],
                           m_config.text_background_color[2]),
                m_ft2);
        }
        if (m_config.draw_non_target_rect) // 是否需要渲染非目标
        {
            for (auto &object : in_valid_targets)
            {
                std::stringstream ss;
                ss << (label_map.find(object.label) == label_map.end() ? "" : label_map[object.label]);
                if (m_config.draw_confidence)
                {
                    ss.precision(0);
                    ss << std::fixed << (label_map.find(object.label) == label_map.end() ? "" : ":")
                       << object.score * 100 << "%";
                }
                cv::Rect rect =
                    cv::Rect{static_cast<int>(object.x1), static_cast<int>(object.y1),
                             static_cast<int>(object.x2 - object.x1), static_cast<int>(object.y2 - object.y1)};
                DrawRectText(m_output_frame, rect, ss.str(), m_config.target_rect_line_thickness, cv::LINE_AA,
                             cv::Scalar(m_config.non_target_rect_color[0], m_config.non_target_rect_color[1],
                                        m_config.non_target_rect_color[2]),
                             m_config.non_target_rect_color[3], m_config.target_text_height,
                             cv::Scalar(m_config.text_foreground_color[0], m_config.text_foreground_color[1],
                                        m_config.text_foreground_color[2]),
                             cv::Scalar(m_config.text_background_color[0], m_config.text_background_color[1],
                                        m_config.text_background_color[2]),
                             m_ft2);
            }
        }
    }

    if (is_need_alert && m_config.draw_warning_text) // 是否需要渲染warning字样
    {
        DrawText(m_output_frame, m_config.warning_text_map[m_config.language], m_config.warning_text_size,
                 cv::Scalar(m_config.warning_text_foreground_color[0], m_config.warning_text_foreground_color[1],
                            m_config.warning_text_foreground_color[2]),
                 cv::Scalar(m_config.warning_text_background_color[0], m_config.warning_text_background_color[1],
                            m_config.warning_text_background_color[2]),
                 m_config.warning_text_left_top, m_ft2);
    }

    // 将结果封装成json字符串
    bool json_alert_code = JSON_ALERT_FLAG_FALSE;
    if (is_need_alert)
    {
        json_alert_code = JSON_ALERT_FLAG_TRUE;
    }
    ev::Value json_root;
    ev::Value json_algo_value;
    ev::Value json_detect_value;
    json_root["cid"] = m_config.cid;
    json_root[JSON_ALERT_FLAG_KEY] = json_alert_code;
    json_root["time_elapsed"] = time_elapsed;
    json_algo_value[JSON_ALERT_FLAG_KEY] = json_alert_code;
    json_algo_value["target_info"].resize(0);
    for (int j = 0; j < valid_targets.size(); j++)
    {
        const auto &obj = valid_targets[j];
        ev::Value tmp_value;
        tmp_value["x"] = int(obj.x1);
        tmp_value["y"] = int(obj.y1);
        tmp_value["width"] = int(obj.x2 - obj.x1);
        tmp_value["height"] = int(obj.y2 - obj.y1);
        tmp_value["name"] = (label_map.find(obj.label) == label_map.end() ? "obj" : label_map[obj.label]);
        tmp_value["confidence"] = obj.score;
        tmp_value["roi"] = target_roi_indexs[j] < m_config.current_roi_orig_polygons_names.size()
                               ? m_config.current_roi_orig_polygons_names[target_roi_indexs[j]]
                               : "";
        json_algo_value["target_info"].append(tmp_value);
    }
    json_root["algorithm_data"] = json_algo_value;

    // create model data
    json_detect_value["objects"].resize(0);
    for (auto &obj : detected_objects)
    {
        ev::Value tmp_value;
        tmp_value["x"] = int(obj.x1);
        tmp_value["y"] = int(obj.y1);
        tmp_value["width"] = int(obj.x2 - obj.x1);
        tmp_value["height"] = int(obj.y2 - obj.y1);
        tmp_value["name"] = (label_map.find(obj.label) == label_map.end() ? "obj" : label_map[obj.label]);
        tmp_value["confidence"] = obj.score;
        json_detect_value["objects"].append(tmp_value);
    }
    json_root["model_data"] = json_detect_value;

    ev::StreamWriterBuilder writer_builder;
    writer_builder.settings_["precision"] = 2;
    writer_builder.settings_["emitUTF8"] = true;
    std::unique_ptr<ev::StreamWriter> json_writer(writer_builder.newStreamWriter());
    std::ostringstream os;
    json_writer->write(json_root, &os);
    m_str_out_json = os.str();
    // 注意：JiEvent.code需要根据需要填充，切勿弄反
    if (is_need_alert)
    {
        event.code = JISDK_CODE_ALARM;
    }
    else
    {
        event.code = JISDK_CODE_NORMAL;
    }
    event.json = m_str_out_json.c_str();
    REC_TIME(t1);
    SDKLOG(INFO) << "output processing time(ms):" << RUN_TIME(t1 - t0);
    return JISDK_RET_SUCCEED;
}
